<?php
declare(strict_types=1);

namespace Kangcg\Application\Model;

/**
 *如何需要 监听调用框架的ORM的方法 请加前缀 handleNewQuery  并且返回 newQuery
 */
trait OrmOperationTrait
{
    /**
     * 使用示例 field 代表数据库字段， keg代表表单字段  with代表ORM的关联
     * [
     *      field,
     *      [field, 条件]，
     *      [field, 条件， "--{field}--"]，
     *      key => field,
     *      key => [field, 条件]，
     *      key => [field, 条件， "--{field}--"]，
     *      with.field ...
     *      or => [
     *          ...
     *     ]
     * ]
     * @var array
     */
    protected array $search = [];
    protected array|string|null $with = null; //关联
    protected array|string $columns = ['*']; //查询字段
    protected array $defaultSort = ['id' => 'DESC']; //默认排序
    protected array $allowSort = []; //允许前台排序字段
    protected int $defaultLimit = 15;
    protected int $maxLimit = 150;
    protected string $pageName = 'page';
    protected string $limitName = 'limit';

    //ORM 映射方法
    protected array $ormMapActions = [
        'with' => 'with',
        'query' => 'query',
        'fill' => 'fill',
        'save' => 'save',
        'first' => 'first',
        'where' => 'where',
        'or' => 'orWhere',
        'in' => 'whereIn',
        'orderBy' => 'orderBy',
        'paginate' => 'paginate',
        'getKeyName' => 'getKeyName',
        'makeHidden' => 'makeHidden',
        'delete' => 'delete',

        //以下是分页映射
        'currentPage' => 'currentPage',
        'lastPage' => 'lastPage',
        'total' => 'total',
        'items' => 'items',
    ];

    /**
     * 返回类的名称 Model::class
     * @return string
     */
    public abstract function getOrmModelClass(): string;

    public abstract function beginTransaction();

    public abstract function rollBack();

    public abstract function commit();

    /**
     * 列表查询方法
     * @param array $input
     * @return array
     */
    public function search(array $input = [])
    {
        $newQuery = $this->getNewQuery();
        $newQuery = $this->handleWith($newQuery);
        $newQuery = $this->handleSearch($this->search, $input, $newQuery);
        $newQuery = $this->handleOrder($newQuery, $this->handleParseSort($input, $this->defaultSort, $this->allowSort));

        return $this->handlePaginate($newQuery, $this->getLimit($input, $this->limitName), $this->columns, $this->pageName);
    }

    /**
     * 数据添加
     * @param array $data
     * @param $loginUser
     * @return false|mixed
     */
    public function store(array $data, $loginUser = null)
    {
        $data = $this->handleInput($data);
        $this->beginTransaction();
        try {
            $this->beforeStore($data, $loginUser);
            $model = $this->getModel();
            $this->handleNewQueryAction($model, 'fill', $data);
            if (!$this->handleNewQueryAction($model, 'save')) {
                $this->rollBack();
                return $this->setError($this->toErrorMsg('store'));
            }

            $this->afterStore($model, $data, $loginUser);
            $this->commit();
        } catch (\Exception $exception) {
            $this->rollBack();
            return $this->setError($exception->getMessage());
        }

        return $model;
    }

    /**
     * 数据修改
     * @param int $id
     * @param array $data
     * @param $loginUser
     * @param $field
     * @param $isCheck
     * @param string $action
     * @return false|mixed
     */
    public function update(int $id, array $data, $loginUser = null, $field = 'client_id', $isCheck = false, string $action = 'update')
    {
        try {
            if (!$dept = $this->getByPrimaryKey($id)) {
                return $this->setError($this->toErrorMsg('find'));
            }

            if ($isCheck && $loginUser && $dept->{$field} != $loginUser->id) {
                return $this->setError($this->toErrorMsg('check'));
            }

            $data = $this->handleInput($data);
            $this->beginTransaction();
            $this->beforeUpdate($dept, $data, $loginUser, $action);
            $this->handleNewQueryAction($dept, 'fill', $data);

            if (!$this->handleNewQueryAction($dept, 'save')) {
                $this->rollBack();
                return $this->setError($this->toErrorMsg('update'));
            }

            $this->afterUpdate($dept, $data, $loginUser, $action);
            $this->commit();
        } catch (\Exception $exception) {
            $this->rollBack();
            return $this->setError($exception->getMessage());
        }

        return $dept;
    }

    /**
     * 数据删除
     * @param int $id
     * @param $loginUser
     * @param $field
     * @return bool
     */
    public function delete(int $id, $loginUser = null, $field = '')
    {
        if (!$dept = $this->getByPrimaryKey($id)) {
            return $this->setError($this->toErrorMsg('find'));
        }

        if ($field && $loginUser && $dept->{$field} != $loginUser->id) {
            return $this->setError($this->toErrorMsg('check'));
        }

        $this->beginTransaction();
        try {
            $this->beforeDelete($dept, $loginUser);
            if (!$this->handleNewQueryAction($dept, 'delete')) {
                $this->rollBack();
                return $this->setError($this->toErrorMsg('delete'));
            }

            $this->afterDelete($dept, $loginUser);
            $this->commit();
        } catch (\Exception $exception) {
            $this->rollBack();
            return $this->setError($exception->getMessage());
        }

        return true;
    }

    /**
     * 获取详情
     * @param int $id
     * @param $hidden
     * @param $with
     * @param $loginUser
     * @return false|mixed
     */
    public function getDetail(int $id, $hidden = [], $with = null, $loginUser = null)
    {
        if (!$dept = $this->getByPrimaryKey($id, $with)) {
            return $this->setError($this->toErrorMsg('find'));
        }

        !empty($hidden) or $this->handleNewQueryAction($dept, 'makeHidden', $hidden);
        $this->afterDetail($dept, $loginUser);

        return $dept;
    }

    /**
     * 单字段更新
     * @param int $id
     * @param string $field
     * @param $value
     * @param $loginUser
     * @param $userField
     * @param $isCheck
     * @return false|mixed
     */
    public function updateField(int $id, string $field, $value, $loginUser = null, $userField = 'client_id', $isCheck = false)
    {
        return $this->update($id, [$field => $value], $loginUser, $userField, $isCheck, $field);
    }

    protected function afterDetail(&$dept, $user)
    {

    }

    protected function beforeUpdate($dept, &$data, $user, $action)
    {

    }

    protected function afterUpdate($dept, $data, $user, $action)
    {

    }

    protected function beforeStore(&$data, $user)
    {

    }

    protected function afterStore($model, $data, $user)
    {
    }

    protected function afterDelete($dept, $loginUser)
    {

    }

    protected function beforeDelete($dept, $user)
    {

    }

    /**
     * 处理输入参数：递归过滤所有层级的参数值
     * @param array $input 待处理的输入数组（支持嵌套数组）
     * @return array 过滤后的输入数组
     */
    private function handleInput(array $input = []): array
    {
        $filteredInput = [];
        foreach ($input as $key => $value) {
            // 递归处理嵌套数组（如 input['user']['name'] 这类结构）
            if (is_array($value)) {
                $filteredInput[$key] = $this->handleInput($value);
            } else {
                // 过滤单个值（处理 null、空值等）
                $filteredInput[$key] = $this->inputFilter($value);
            }
        }

        return $filteredInput;
    }

    /**
     * 单个值过滤：处理 null 并统一空值格式
     * @param mixed $value 待过滤的值（可能是字符串、数字、null 等）
     * @return mixed 过滤后的值（null 转为空字符串，其他值保留类型）
     */
    private function inputFilter($value)
    {
        if (is_null($value)) {
            return '';
        }

        if (is_string($value)) {
            $value = trim($value);
        }

        return $value;
    }

    public function getByPrimaryKey(mixed $primaryKey, mixed $with = null)
    {
        $key = $this->getModel()->getKeyName();
        return $this->getBy([$key => $primaryKey], $with);
    }

    public function getBy(array $where, $with = null)
    {
        $query = $this->handleNewQueryAction($this->getModel(), 'where', $where);
        !$with or $query = $this->handleNewQueryAction($query, 'with', $with);

        return $this->handleNewQueryAction($query, 'first');
    }

    protected function afterSearchList(&$items)
    {

    }

    protected function handleOrder(object $newQuery, $sorts): object
    {
        foreach ($sorts as $field => $sort) {
            $newQuery = $this->handleNewQueryAction($newQuery, 'orderBy', $field, $sort);
        }

        return $newQuery;
    }

    protected function handlePaginate($newQuery, int $limit, $columns, $pageName)
    {
        $paginate = $this->handleNewQueryAction($newQuery, 'paginate', $limit, $columns, $pageName);
        $data['current_page'] = $this->handleNewQueryAction($paginate, 'currentPage');
        $data['last_page'] = $this->handleNewQueryAction($paginate, 'lastPage');
        $data['total'] = $this->handleNewQueryAction($paginate, 'total');
        $data[$this->limitName] = $limit;
        $data['list'] = $this->handleNewQueryAction($paginate, 'items');

        $this->afterSearchList($data['list']);

        return $data;
    }

    protected function handleWith(object $newQuery)
    {
        if (!empty($this->with)) {
            return $this->handleNewQueryAction($newQuery, 'with', $this->with);
        }

        return $newQuery;
    }

    private function getModel()
    {
        $modelClass = $this->getOrmModelClass();
        if (!class_exists($modelClass)) {
            throw new \RuntimeException("模型类不存在：{$modelClass}");
        }

        return new $modelClass();
    }

    /**
     * @return object Laravel | TP | YII等的查询ORM
     */
    private function getNewQuery(): object
    {
        $modelClass = $this->getOrmModelClass();
        if (!class_exists($modelClass)) {
            throw new \RuntimeException("模型类不存在：{$modelClass}");
        }

        $method = $this->ormMapActions['query'] ?? 'query';
        return $modelClass::$method();
    }

    private function getLimit($input, $key): int
    {
        $limit = isset($input[$key]) ? intval($input[$key]) : $this->defaultLimit;
        return $limit <= 0 ? $this->defaultLimit : ($limit >= $this->maxLimit ? $this->maxLimit : $limit);
    }

    private function handleNewQueryAction(object $newQuery, $action, ...$args)
    {
        $action = $this->ormMapActions[$action] ?? $action;
        $traitAction = 'handleNewQuery' . ucfirst($action);
        if (method_exists($this, $traitAction)) {
            array_unshift($args, $newQuery);
            return call_user_func_array([$this, $traitAction], $args);
        }

        if (method_exists($newQuery, $action)) {
            return call_user_func_array([$newQuery, $action], $args);
        }

        throw new \RuntimeException("{$action} 无法查询使用，ORM 或者 类本身未支持！");
    }

    private function handleParseSort(array $input, array $defaultSort, $allowSort)
    {
        if (!$sortsStr = (isset($input['sort']) ? trim($input['sort']) : '')) {
            return $defaultSort;
        }

        $sortItems = explode(',', $sortsStr);
        foreach ($sortItems as $item) {
            $item = trim($item);
            if (empty($item)) {
                continue; // 跳过空项（如参数末尾多写的逗号）
            }

            // 分割字段名和排序方向（最多分割1次，避免字段名含"="的异常情况）
            $parts = explode('=', $item, 2);
            $field = trim($parts[0]);
            // 处理排序方向：无值时默认 DESC，值不合法时强制 DESC
            $direction = isset($parts[1]) ? trim($parts[1]) : 'DESC';
            $direction = in_array(strtoupper($direction), ['ASC', 'DESC']) ? strtoupper($direction) : 'DESC';
            // 仅保留允许排序的字段
            if (in_array($field, $allowSort)) {
                $orders[$field] = $direction;
            }
        }

        return count($orders) > 0 ? $orders : $defaultSort;
    }

    private function handleSearch(array $searchItems, array $input, object $newQuery)
    {
        foreach ($searchItems as $key => $search) {
            if ($this->isClosure($search)) {
                $newQuery = $this->isString($key) ? $this->handleNewQueryAction($newQuery, 'where', $key, $search)
                    : $search($newQuery, $input);

            } else if ($this->isInt($key)) {
                $newQuery = $this->handleQueryItem($newQuery, $input, $search);
            } else if ($this->isInputMapField($key) && $this->isInputFieldValueNotEmpty($key, $input)) {
                $newQuery = $this->handleParseQueryMap($newQuery, $input, $key, $search);
            } else if ($this->isOr($key)) {
                $newQuery = $this->handleNewQueryAction($newQuery, 'or', function ($newQuery) use ($search, $input) {
                    $this->handleSearch($search, $input, $newQuery);
                });
            }
        }

        return $newQuery;
    }

    private function handleParseQueryMap(object $newQuery, array $input, string $key, array|string $field)
    {
        if ($this->isString($field)) {
            $input[$field] = $input[$key];
            $field = [$field, '=', $input[$key]];
        } else {
            $input[$field[0]] = $input[$key];
        }

        return $this->handleQueryItem($newQuery, $input, $field);
    }

    private function handleQueryItem(object $newQuery, $input, $search)
    {
        if ($this->isSearchFieldAction($search, $action)) {
            $newQuery = $this->{$action}($newQuery, $input);
        } else if ($this->isOr($search)) {
            $orWhere = $search['or'];
            unset($search['or']);

            $this->handleNewQueryAction($newQuery, 'where', function ($newQuery) use ($search, $input) {
                $this->handleSearch($search, $input, $newQuery);
            });

            $this->handleNewQueryAction($newQuery, 'or', function ($newQuery) use ($orWhere, $input) {
                $this->handleSearch($orWhere, $input, $newQuery);
            });
        } else if ($this->isSearchFieldIsString($search, $input)) { //$field 是字符串
            $newQuery = $this->parseQueryHandle($newQuery, $search, '=', $input[$search]);
        } else if ($this->isSearchFieldIsArray($search, $input)) {  //$field 是数组
            $newQuery = $this->parseQueryHandle($newQuery, $search[0], $search[1], $search[2]);
        }

        return $newQuery;
    }

    private function parseQueryHandle(object $newQuery, $field, $operator, $value)
    {
        list($field, $relation) = $this->parseRelationAndField($field);
        if ($relation === null) {
            return is_array($value) ?
                $this->handleNewQueryAction($newQuery, 'in', $field, $value) :
                $this->handleNewQueryAction($newQuery, 'where', $field, $operator, $value);
        }

        return $this->handleNewQueryAction($newQuery, 'has', $relation, function ($newQuery) use ($field, $operator, $value) {
            is_array($value) ? $this->handleNewQueryAction($newQuery, 'in', $field, $value)
                : $this->handleNewQueryAction($newQuery, 'where', $field, $operator, $value);
        });
    }

    /**
     * 输入值是否为空
     * @param $field
     * @param $input
     * @return bool
     */
    private function isInputFieldValueNotEmpty($field, $input): bool
    {
        if (!isset($input[$field])) {
            return false;
        }

        return $this->isValueEmpty($input[$field]) === false;
    }

    /**
     * 是否是字段映射查询
     * @param $key
     * @return bool
     */
    private function isInputMapField($key)
    {
        if (!$this->isString($key) || $this->isOr($key)) {
            return false;
        }

        return true;
    }

    /**
     * 是否是 数组组合查询
     * @param $field
     * @param array $input
     * @return bool
     */
    private function isSearchFieldIsArray(&$field, array $input): bool
    {
        if (!is_array($field)) {
            return false;
        }

        $column = $field[0];
        list($column) = $this->parseRelationAndField($column);
        if (!isset($field[1])) {
            $field[1] = '=';
        }

        //表单没有值返回 false;
        if (!isset($input[$column]) || $this->isValueEmpty($input[$column])) {
            return false;
        }

        //判断是否给与了第三列，替换或者默认值
        $bool = isset($field[2]);
        if ($bool === false) {
            $field[2] = $input[$column];
        }

        //判断给与第三列值是否需要替换
        if ($bool && strrpos($field[2], "--{$column}--") !== false) {
            $field[2] = $this->replace($column, $input[$column], $field[2]);
        }

        return true;
    }

    /**
     * @param string $column
     * @return array
     */
    private function parseRelationAndField(string $column): array
    {
        $relation = null;
        if (strrpos($column, ".") > 0) {
            $columns = explode('.', $column);
            $len = count($columns) - 1;
            $column = $columns[$len];
            unset($columns[$len]);
            $relation = implode('.', $columns);
        }

        return [
            $column,
            $relation
        ];
    }

    /**
     * 组合查询替换
     * @param $search
     * @param $replace
     * @param $subject
     * @return array|string|string[]
     */
    private function replace($search, $replace, $subject)
    {
        return str_replace("--{$search}--", $replace, $subject);
    }

    /**
     * 是否是字段查询方法
     * @param string|array $field
     * @param array $input
     * @return bool
     */
    private function isSearchFieldIsString(string|array $field, array $input): bool
    {
        if ($this->isString($field) === false) {
            return false;
        }

        if (!isset($input[$field])) {
            return false;
        }

        if ($this->isValueEmpty($input[$field])) {
            return false;
        }

        return true;
    }

    /**
     * 是否OR查询
     * @param $key
     * @return bool
     */
    private function isOr($key): bool
    {
        if (is_array($key)) {
            return isset($key['or']);
        }

        return $key === 'or';
    }

    /**
     * 判断类是否存在字段查询方法
     * @param $field
     * @param $action
     * @return bool
     */
    private function isSearchFieldAction($field, &$action = ''): bool
    {
        if ($this->isString($field) === false) {
            return false;
        }

        $action = 'search' . ucfirst($field);
        return method_exists($this, $action);
    }

    /**
     * @param array $input
     * @param string $field
     * @return bool
     */
    private function isInputFieldNotEmpty(array $input, string $field): bool
    {
        if (!isset($input[$field])) {
            return false;
        }

        return !$this->isValueEmpty($input[$field]);
    }

    /**
     * 判断是否是空值
     * @param $value
     * @return bool
     */
    private function isValueEmpty($value)
    {
        return $value === '' || $value === null;
    }

    /**
     * 是否是回调函数
     * @param $value
     * @return bool
     */
    private function isClosure($value): bool
    {
        return $value instanceof \Closure;
    }

    /**
     * @param $value
     * @return bool
     */
    private function isString($value): bool
    {
        return is_string($value);
    }

    /**
     * @param $value
     * @return bool
     */
    private function isInt($value): bool
    {
        return is_int($value);
    }

    protected function setError($msg, $code = '-1', $data = null)
    {
        $this->_errors[] = [
            'code' => $code,
            'msg' => $msg,
            'data' => $data,
        ];

        return false;
    }

    public function getErrorMsg()
    {
        return $this->_errors[0]['msg'] ?? 'fail';
    }

    public function getErrorCode()
    {
        return $this->_errors[0]['code'] ?? '-1';
    }

    public function getErrors()
    {
        $errors = current($this->_errors);
        $errors = empty($errors) ? [
            'code' => '-1',
            'msg' => 'fail',
            'data' => null,
        ] : $errors;

        return $errors;
    }

    protected function toErrorMsg(string $event): string
    {
        return $this->errMsg[$event] ?? $event;
    }

    protected $errMsg = [
        'save' => '数据保存失败！',
        'find' => '数据未找到！',
        'check' => '数据异常！',
        'update' => '数据更新失败！',
        'delete' => '数据删除失败！',
    ];

    private $_errors = [];
}